interface IScoreUpdatedEvent extends IEvent {
    on(listener :(player? :Player, score? :number) => void) :void;
    once(listener :(player? :Player, score? :number) => void) :void;
    off(listener :(player? :Player, score? :number) => void) :void;
    fire(player? :Player, score? :number) :void;
}

interface IRemainingTrainsUpdatedEvent extends IEvent {
    on(listener :(player? :Player, remainingTrains? :number) => void) :void;
    once(listener :(player? :Player, remainingTrains? :number) => void) :void;
    off(listener :(player? :Player, remainingTrains? :number) => void) :void;
    fire(player? :Player, remainingTrains? :number) :void;
}

class TTRScorer {

    public scoreUpdated :IScoreUpdatedEvent;
    public remainingTrainsUpdated :IRemainingTrainsUpdatedEvent;

    // Constant state.
    private board :Board;
    private players :Player[];
    private trainCount :number;

    // Transient state.
    private trackClaims :TrackClaim[];
    private playerScores :{[playerId :string] :number;};
    private remainingTrains :{[playerId :string] :number;};

    constructor(board :Board, playerCount :number, trainCount :number) {
        if (board === undefined || board === null)
            throw ("board may not be undefined or null.");
        if (playerCount === undefined || playerCount === null)
            throw ("playerCount may not be undefined or null.");
        if (playerCount < 2)
            throw ("playerCount must be two or more.");

        if (trainCount === undefined || trainCount === null)
            trainCount = 45;

        this.scoreUpdated = new TypedEvent();
        this.remainingTrainsUpdated = new TypedEvent();

        this.board = board;
        this.players = [];
        this.trainCount = trainCount;

        this.trackClaims = [];
        this.playerScores = {};
        this.remainingTrains = {};

        var playerIndex :number = 0;
        for(; playerIndex < playerCount; ++playerIndex) {
            this.players.push(new Player());
        }
        this.reset();
        //    this.playerScores[this.players[playerIndex].getId().toString()] = 0;
        //    this.remainingTrains[this.players[playerIndex].getId().toString()] = this.trainCount;
        //}
    }

    public getBoard() :Board {
        return this.board;
    }

    public getPlayerIds() :number[] {
        var playerIds :number[] = [];

        var playerIndex :number = 0;
        for(; playerIndex < this.players.length; ++playerIndex) {
            playerIds.push(this.players[playerIndex].getId());
        }

        return playerIds;
    }

    public containsPlayer(playerId :number) :boolean {
        if (playerId === undefined || playerId === null)
            throw ("playerId parameter may not be undefined or null.");

        return (this.getPlayer(playerId) !== undefined);
    }

    public getPlayer(playerId :number) :Player {
        if (playerId === undefined || playerId === null)
            throw ("playerId parameter may not be undefined or null.");

        var playerIndex :number = 0;
        for(; playerIndex < this.players.length; ++playerIndex) {
            if (this.players[playerIndex].getId() === playerId) {
                return this.players[playerIndex];
            }
        }

        return undefined;
    }

    public getPlayerScore(playerId :number) :number {
        if (playerId === undefined || playerId === null)
            throw ("playerId parameter may not be undefined or null.");

        if (this.containsPlayer(playerId)) {
            return this.playerScores[playerId.toString()];
        }
    }

    public getPlayerScores() :{[playerId :string] :number;} {
        var playerScores :{[playerId: string] :number;} = {};

        var playerIndex :number = 0;
        for(; playerIndex < this.players.length; ++playerIndex) {
            var playerId :number = this.players[playerIndex].getId();
            playerScores[playerId] = this.playerScores[playerId.toString()];
        }

        return playerScores;
    }

    public getRemainingTrains(playerId :number) :number {
        if (playerId === undefined || playerId === null)
            throw ("playerId parameter may not be undefined or null.");

        if (this.containsPlayer(playerId)) {
            return this.remainingTrains[playerId.toString()];
        }
    }

    public claim(track :Track, playerId :number) :void {
        if (track === undefined || track === null)
            throw ("track parameter may not be undefined or null.");
        if (playerId === undefined || playerId === null)
            throw ("playerId parameter may not be undefined or null.");

        if (!this.board.containsTrack(track))
            throw ("Attempt to claim unknown track from (" + track.getFrom() + ") to (" + track.getTo() + ").");

        var player = this.getPlayer(playerId);
        if (player === undefined)
            throw ("Attempt to claim track for unknown player (" + playerId + ").");

        if (this.isClaimed(track))
            throw ("Attempt to claim track which was already claimed.");

        if (this.remainingTrains[playerId.toString()] < track.getSegmentCount())
            throw ("Player does not have enough trains to claim track.");

        var trackClaim :TrackClaim = new TrackClaim(track, player);
        this.trackClaims.push(trackClaim);

        this.playerScores[playerId.toString()] += track.getScore();
        this.remainingTrains[playerId.toString()] -= track.getSegmentCount();

        this.scoreUpdated.fire(player, this.playerScores[playerId.toString()]);
        this.remainingTrainsUpdated.fire(player, this.remainingTrains[playerId.toString()]);
    }

    public isClaimed(track :Track) :boolean {
        var trackClaimIndex :number = 0;
        for(; trackClaimIndex < this.trackClaims.length; ++trackClaimIndex) {
            if (this.trackClaims[trackClaimIndex].getTrack() === track) {
                return true;
            }
        }

        return false;
    }

    public reset() :void {
        this.trackClaims = [];

        var playerIndex :number = 0;
        for(; playerIndex < this.players.length; ++playerIndex) {
            var player = this.players[playerIndex];

            this.playerScores[player.getId().toString()] = 0;
            this.remainingTrains[player.getId().toString()] = this.trainCount;

            this.scoreUpdated.fire(player, 0);
            this.remainingTrainsUpdated.fire(player, this.trainCount);
        }
    }
}
